/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package thecliffproblem;

import GUI.ActionObserver;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.Position;

/**
 *
 * @author Jaime
 */
public class Board {
        
    /////////////////////////////////////////////////
    ///// BOARD CONFIGURATION
    private int numberOfLines = 4;
    private int numberOfColumns = 12;
    
    
    // Variables that define cliff square, considering a cliff that
    // is in one line only and goes sequentially (no gaps) through many columns.
    private static final int cliffLine = 3;
    private static final int firstCliffColumn = 1;
    private static final int lastCliffColumn = 10;
    //////////////////////////////////////////////
    
    ////// OTHER CONFIGURATION
    private static final double DISCOUNT_FACTOR = 0.9;
    private double alpha = 1;
    //////////////////////////////////
    
    ////// ATTRIBUTES    
    // initial and final square coordinate:
    private Coordinate initialCoordinate;
    private Coordinate finalCoordinate;
    ///////////////////////////////////////////////
    
    private Square[][] board;
    private Coordinate currentCoordinate;
    
    // Observers
    List<ActionObserver> observers;
    
    public Board()
    {
        observers = new ArrayList<ActionObserver> ();
        
        //Make sure initial and final coordinate are not with cliff! Also, make sure
        // they're different!
        initialCoordinate = new Coordinate(3,0);
        finalCoordinate = new Coordinate(3, 11);
        
        board = new Square[numberOfLines][numberOfColumns];
        
        for(int i = 0; i < numberOfLines; i++)
        {
            for(int j = 0; j <  numberOfColumns; j++)
            {
                               
                Coordinate coordinate = new Coordinate(i, j);
                
                boolean initial, finalSquare, cliff;
                
                initial = initialCoordinate.equals(coordinate);
                finalSquare = finalCoordinate.equals(coordinate);
                
                cliff = coordinate.isWithin(new Coordinate(cliffLine, 
                        firstCliffColumn), new Coordinate(cliffLine, lastCliffColumn));
                
                board[i][j] = new Square(cliff, cliff || finalSquare, coordinate);
                
            }
        }
        
        currentCoordinate = initialCoordinate;
        
    }
    
            
    
    /**
     * Returns the coordinate to which the action provided would lead.
     * @param a The action
     * @return The coordinate to which the action leads.
     */
    private Coordinate actionToCoordinate(Action a)
    {
        int line = currentCoordinate.getLine();
        int column = currentCoordinate.getColumn();
        
        switch(a)
        {
            case UP:
                line--;
                break;
            case DOWN:
                line++;
                break;
            case LEFT:
                column--;
                break;
            case RIGHT:
                column++;
                break;
        }
        
        line = line < 0 ? 0 : line;
        line = line >= getNumberOfLines() ? getNumberOfLines() -1 : line;
        column = column < 0 ? 0 : column;
        column = column >= getNumberOfColumns() ? getNumberOfColumns() -1 : column;
        
        return new Coordinate(line, column);
        
    }
   
    
    
    
    private boolean episodeStep()
    {
        Square currentSquare = getSquare(currentCoordinate);
        
        if(currentSquare.isTerminal())
            return false;
        
        // a
        Action chosen = currentSquare.greedlyChooseNextBestAction();
        Coordinate next = actionToCoordinate(chosen);
        // s'
        Square nextSquare = getSquare(next);
        
        // Rt+1
        double reward = nextSquare.getReward();
        
        // Q(s,a)
        double currentStateActionEvaluation = currentSquare.getActionEvaluation(chosen);
        
        // a'
        Action nextStatesBestAction = nextSquare.greedlyChooseNextBestAction();
        
        //Q(s',a')
        double nextStateActionEvaluation = nextSquare.getActionEvaluation(nextStatesBestAction);
        
        // New Q(s,a) = Q(s,a) + alpha*[r+discount*Q(s',a')-Q(s,a)];
        double newCurrentStateActionEvaluation = currentStateActionEvaluation + alpha*
                (reward + DISCOUNT_FACTOR*nextStateActionEvaluation - currentStateActionEvaluation);
        
        // Setting new evaluation
        currentSquare.setActionEvaluation(chosen, newCurrentStateActionEvaluation);
        
        // updating observers
        observeNewEvaluation(chosen, currentCoordinate, newCurrentStateActionEvaluation);
        
        Coordinate aux = currentCoordinate;
        
        currentCoordinate = next;
        
        // updating observer
        observeAction(aux, next);
        
        
        return true;
    }
        
    private void resetEpisode()
    {
        currentCoordinate = initialCoordinate;
    }
    
    public int nextEpisode(boolean show, long delay) throws InterruptedException
    {
        
        int steps = 0;
        
        boolean status = true;
        
        while(status)
        {
            //if(show)
            //System.out.println(this);
        
            status = episodeStep();
            if(show)
                Thread.sleep(delay);
            steps++;
            
        }
        
        
         resetEpisode();      
        // updating observers
        observeEpisodeEnd();
        
        return steps;
        
    }
    
    
    /**
     * Returns the position in a specific coordinate
     * @param coordinate The coordinate in which the position is.
     * @return The position in the coordinate;
     */
    private Square getSquare(Coordinate coordinate)
    {
        return board[coordinate.getLine()][coordinate.getColumn()];
    }
    
    public void reset()
    {
        resetEpisode();
        for(Square[] sqs:board)
        {
            for(Square sq:sqs)
            {
                sq.reset();
                observeNewEvaluation(Action.UP, sq.getCoordinate(), 0);
                observeNewEvaluation(Action.DOWN, sq.getCoordinate(), 0);
                observeNewEvaluation(Action.RIGHT, sq.getCoordinate(), 0);
                observeNewEvaluation(Action.LEFT, sq.getCoordinate(), 0);
                
            }
        }
        observeEpisodeEnd();
        
    }
                
        // OBSERVERS METHODS:
    public void registerObserver(ActionObserver observer)
    {
        observers.add(observer);
    }
    
    private void observeAction(Coordinate origin, Coordinate destination)
    {
        for(ActionObserver obs:observers)
        {
            obs.step(origin, destination);
        }
    }
    
    private void observeEpisodeEnd()
    {
        for(ActionObserver obs:observers)
        {
            obs.episodeEnd();
        }
    }
    
    private void observeNewEvaluation(Action action, Coordinate square, double value)
    {
        for(ActionObserver obs:observers)
        {
            obs.newEvaluation(action, square, value);
        }
    }
    
    
    //////////////////////////////////////
    
    
    public String toString()
    {
        String toString = "";
        
        for(int i = 0; i < getNumberOfLines(); i++)
        {
            toString += "|";
            for(int j = 0; j < getNumberOfColumns(); j++)
            {
                Coordinate coor = new Coordinate(i, j);
                
                if(currentCoordinate.equals(coor))
                {
                    toString += " X |";
                }
                else
                {
                    if(getSquare(coor).isCliff())
                    {
                        toString += " C |";
                    }
                    else
                    {
                        if(getSquare(coor).isGoal())
                        {
                            toString += " G |";
                        }
                        else
                        {
                            if(coor.equals(initialCoordinate))
                            {
                                toString += " S |";
                            }
                            else
                            {
                                toString += "   |";
                            }
                        }
                    }
                }
                
            }
            toString += "\n";
            
           
        }
        
        toString += "\n"+evaluationMatrixToString();
        
        return toString;
        
    }

   
    public String evaluationMatrixToString()
    {
        String toString = "| U; D; R; L; |\n";
        DecimalFormat df = new DecimalFormat("0.00");
        
        for(int i = 0; i < getNumberOfLines(); i++)
        {
            toString += "|";
            for(int j = 0; j < getNumberOfColumns(); j++)
            {
                Coordinate coor = new Coordinate(i, j);
                Square square = getSquare(coor);
                toString += " "+df.format(square.getActionEvaluation(Action.UP))+"; ";
                toString += " "+df.format(square.getActionEvaluation(Action.DOWN))+"; ";
                toString += " "+df.format(square.getActionEvaluation(Action.RIGHT))+"; ";
                toString += " "+df.format(square.getActionEvaluation(Action.LEFT))+"; |";
                
            }
            toString += "\n";
        }
        return toString;
    }
    
   

    /**
     * @return the numberOfLines
     */
    public int getNumberOfLines() {
        return numberOfLines;
    }

    /**
     * @return the numberOfColumns
     */
    public int getNumberOfColumns() {
        return numberOfColumns;
    }
    /**
     * Returns an integer defining the type of the square. It may be:
     * 0: regular empty square.
     * 1: initial square.
     * 2: cliff square.
     * 3: goal square.
     * 4: current square.
     * @param coordinate The coordinate of the square, must be within board.
     * @return The type of the square.
     */
    public int getSquareType(Coordinate coordinate)
    {
        Square square = getSquare(coordinate);
        
        if(coordinate.equals(currentCoordinate))
            return 4;
        
        if(coordinate.equals(initialCoordinate))
            return 1;
        
        if(square.isCliff())
            return 2;
        
        if(square.isGoal())
            return 3;
        
        return 0;
        
    }
    
}
